﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RPGProject.Data_Structures
{
    /// <summary>
    /// An implementation of a priority queue that uses a Heap.  Specify whether adding a duplicate adds it to the end of the queue, or replace the current one if its faster.
    /// </summary>
    /// <typeparam name="TPriority"></typeparam>
    /// <typeparam name="TData"></typeparam>
    class PriorityQueue<TPriority, TData> where TPriority : IComparable<TPriority>
    {
        private Heap<Node> data;
        private bool duplicates;
        private int recentAge;
        public int Size
        {
            get { return this.data.Size; } 
        }
        /// <summary>
        /// Duplicates specifies if you want more priority elements to replace old equivalent enemies.
        /// </summary>
        /// <param name="duplicates"></param>
        public PriorityQueue(bool duplicates = true)
        {
            this.duplicates = duplicates;
            this.recentAge = 0;
            this.data = new Heap<Node>();
        }
        /// <summary>
        /// Get the first element in the queue.
        /// </summary>
        /// <returns></returns>
        public TData Dequeue()
        {
            return this.data.RemoveMin().Data;
        }
        /// <summary>
        /// Enqueue the TData with the TPriority.
        /// </summary>
        /// <param name="priority"></param>
        /// <param name="data"></param>
        public void Enqueue(TPriority priority, TData data)
        {
            if (duplicates)
                this.data.Add(new Node(priority, data, recentAge++));
            else
            {
                Node newNode = new Node(priority, data, recentAge++);
                int index = this.data.IndexOf(newNode);
                if (index == -1)
                {
                    this.data.Add(newNode);
                }
                else
                {
                    TPriority prevPriority = this.data[index].Priority;
                    if (prevPriority.CompareTo(newNode.Priority) > 0)
                    {
                        this.data.Remove(newNode);
                        this.data.Add(newNode);
                    }
                }
            }
        }

        public override string ToString()
        {
            return this.data.ToString();
        }
        /// <summary>
        /// A Node class representing a PQNode.
        /// </summary>
        private class Node : IComparable<Node>
        {
            private TData data;
            private TPriority priority;
            private int age;
            public TData Data
            {
                get { return this.data;}
            }
            public TPriority Priority
            {
                get{ return this.priority;}
            }



            public Node(TPriority priority, TData data, int age)
            {
                this.age = age;
                this.data = data;
                this.priority = priority;
            }

            public override bool Equals(object obj)
            {
                if (obj is Node)
                {
                    Node n = obj as Node;
                    return this.data.Equals(n.data);
                } else 
                    return false;
            }
            public int CompareTo(Node other)
            {
                if (this.priority.CompareTo(other.priority) == 0)
                    return this.age - other.age;
                return this.priority.CompareTo(other.priority);
            }
            public override string ToString()
            {
                return this.data.ToString();
            }
        }
        /// <summary>
        /// Returns the first data without actually taking it off the queue.
        /// </summary>
        /// <returns></returns>
        public TData Peek()
        {
            return this.data.GetMin().Data;
        }

        public static void TMain(String[] args)
        {
            PriorityQueue<int, String> test = new PriorityQueue<int, string>();
            test.Enqueue(0, "Alpha");
            test.Enqueue(0, "Beta");
            test.Enqueue(0, "Alpha");
            test.Enqueue(0, "Gamma");
            test.Enqueue(0, "Beta");
            test.Enqueue(0, "Gamma");
            test.Enqueue(0, "Alpha");
        }

        public void Clear()
        {
            this.data.Clear();
        }
    }
}
